package jmine.tec.hydra.domain.actions;

import java.util.Collections;
import java.util.List;

import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;

import jmine.tec.hydra.HydraMessages;
import jmine.tec.hydra.ReceiveActivationData;
import jmine.tec.hydra.api.DecodeMessageAction;
import jmine.tec.hydra.api.HydraAction;
import jmine.tec.hydra.api.HydraMessage;
import jmine.tec.hydra.api.HydraSpecification;
import jmine.tec.hydra.api.MessageActivationAction;
import jmine.tec.hydra.domain.HydraActivation;
import jmine.tec.hydra.domain.filter.HydraFilterService;
import jmine.tec.hydra.domain.filter.HydraFilterType;
import jmine.tec.hydra.exception.HydraException;
import jmine.tec.hydra.receive.HydraMessageInfo;
import jmine.tec.persist.annotation.DiscriminatorComment;
import jmine.tec.script.ScriptLogger;

import org.apache.commons.lang.StringUtils;

import bancosys.tec.persist.dao.BaseDAO;
import bancosys.tec.persist.dao.BeanNotFoundException;

/**
 * Ação de ativação de mensagens. Efetua uma primeira leitura da mensagem para definir que especificação deve ser executada e armazená-la na
 * conversa correta, através da execução da ativação.
 * 
 * @author lundberg
 */
@Entity
@DiscriminatorValue("1")
@DiscriminatorComment("ATIVACAO POR MENSAGEM")
public class DefaultMessageActivationAction extends AbstractHydraAction implements MessageActivationAction {

    // Transient
    private HydraMessage message;

    /**
     * {@inheritDoc}
     */
    public MessageActivationAction prepare(HydraMessage msg) {
        this.message = msg;
        return this;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected List<HydraAction> doExecute() throws Exception {
        LOGGER.debug(String.format("Executando ativação da mensagem %s para o sistema %s", this.message.getId(), this.getRequest()
                .getSystem().getMnemonico()));
        HydraActivation activation = this.findActivation();
        LOGGER.debug(String.format("Ativação %s encontrada", activation.getMnemonico()));
        HydraMessageInfo messageInfo = this.executeActivation(activation);
        if (messageInfo.hasNoAction()) {
            return Collections.emptyList();
        }
        LOGGER.debug(String.format("Ativação executada: messageId = %s, action = %s", messageInfo.getMessageId(), messageInfo.getAction()));
        HydraSpecification specification = this.findSpecification(messageInfo);
        this.setSpecification(specification);
        LOGGER.debug(String.format("Especificação %s encontrada", specification.getAction()));
        this.applyFilters(specification);
        LOGGER.debug(String.format("Filtros aplicados.", specification.getAction()));
        this.saveMessageWithConversation(messageInfo.getMessageId(), this.message);
        LOGGER.debug(String.format("Mensagem associada à conversa", specification.getAction()));
        this.logAsSystem(String.format("Mensagem associada à conversa %s e enviada para execução da especificação %s.", this.message
                .getConversation().getMnemonico(), specification.getAction()));
        return this.createDecodeAction(specification);
    }

    /**
     * Encontra a ativação apropriada para a mensagem
     * 
     * @return HydraActivation
     */
    private HydraActivation findActivation() {
        BaseDAO<HydraActivation> dao = this.getController().getDAOFactory().getGenericDAO(HydraActivation.class);
        try {
            return dao.findByNaturalKey(this.getRequest().getSystem().getMnemonico());
        } catch (BeanNotFoundException e) {
            throw new HydraException(HydraMessages.RECEIVE_BSH_NO_ACTIVATIONFOUND.create(this.getRequest().getSystem().getMnemonico()), e);
        }
    }

    /**
     * Executa a ativação, resultando em um bean contendo a action que deve ser tomada e a identificação da conversa a qual a mensagem
     * pertence
     * 
     * @param activation activation
     * @return HydraMessageInfo
     */
    private HydraMessageInfo executeActivation(HydraActivation activation) {
        HydraMessageInfo messageInfo = activation.executeReceive(new ReceiveActivationData(this.message));
        this.processLogs(messageInfo.getScriptLogger());
        if (messageInfo == null || (StringUtils.isEmpty(messageInfo.getAction()) && !messageInfo.hasNoAction())) {
            throw new HydraException(HydraMessages.RECEIVE_NO_ACTION_FOUND.create(this.getRequest().getSystem().getMnemonico()));
        }
        return messageInfo;
    }

    /**
     * Encontra a especificação do que deve ser feito com a mensagem
     * 
     * @param messageInfo messageInfo
     * @return HydraSpecificationImpl
     */
    private HydraSpecification findSpecification(HydraMessageInfo messageInfo) {
        try {
            return this.getController().getProvider().findSpecification(this.getRequest().getSystem(), messageInfo.getAction());
        } catch (BeanNotFoundException e) {
            throw new HydraException(HydraMessages.RECEIVE_NO_SPECIFICATION_FOUND.create(messageInfo.getAction(), this.getRequest()
                    .getSystem().getMnemonico()), e);
        }
    }

    /**
     * Aplica os filtros à mensagem, de acordo com a especificação
     * 
     * @param specification specification
     */
    private void applyFilters(HydraSpecification specification) {
        new HydraFilterService(this.message, specification, HydraFilterType.RECEIVING).applyFilters();
    }

    /**
     * Cria a HydraAction que irá decodificar a mensagem de acordo com a especificação
     * 
     * @param specification specification
     * @return List de HydraAction
     */
    private List<HydraAction> createDecodeAction(HydraSpecification specification) {
        DecodeMessageAction action = this.getController().getProvider().createAction(DecodeMessageAction.class);
        action.prepare(this.message);
        return Collections.singletonList((HydraAction) action);
    }

    /**
     * Processa os logs.
     * 
     * @param logger o logger
     */
    private void processLogs(ScriptLogger logger) {
        if (logger == null) {
            return;
        }
        for (String log : logger.getLogs()) {
            this.logAsUser(log);
        }
        if (!StringUtils.isBlank(logger.getTraces())) {
            this.logAsUser(logger.getTraces());
        }
        if (logger.isSuccess()) {
            LOGGER.debug(String.format("Ativação do sistema %s obteve sucesso.", this.getRequest().getSystem().getMnemonico()));
        } else {
            this.markAsPending();
            LOGGER.debug(String.format("Ativação do sistema %s pendente.", this.getRequest().getSystem().getMnemonico()));
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected String createDescription() {
        return "Ativação por recebimento de mensagem.";
    }
}
